﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Jaiden.Security.Cryptography;


namespace Jaiden.Security
{
    public abstract class Hashing
    {
        public static Hashing MD5 { get; } = new HashingImpl(() => new MD5CryptoServiceProvider());
        public static Hashing SHA1 { get; } = new HashingImpl(() => new SHA1CryptoServiceProvider());
        public static Hashing SHA256 { get; } = new HashingImpl(() => new SHA256CryptoServiceProvider());
        public static Hashing SHA384 { get; } = new HashingImpl(() => new SHA384CryptoServiceProvider());
        public static Hashing CRC32 { get; } = new HashingImpl(() => new CRC32());
        public static Hashing CRC64 { get; } = new HashingImpl(() => new CRC64Iso());

        private class HashingImpl : Hashing
        {
            public HashingImpl(Func<HashAlgorithm> hashAlgorithmFactory) : base(hashAlgorithmFactory)
            {
            }
        }


        private readonly Func<HashAlgorithm> _hashAlgorithmFactory;

        private Hashing(Func<HashAlgorithm> hashAlgorithmFactory)
        {
            _hashAlgorithmFactory = hashAlgorithmFactory;
        }


        private HashAlgorithm CreateHashAlgorithm()
        {
            return _hashAlgorithmFactory();
        }

        public string Hash(Stream stream)
        {
            using (var ha = CreateHashAlgorithm())
            {
                var data = ha.ComputeHash(stream);
                return ToString(data);
            }
        }

        public string HashFile(string path)
        {
            using (var fs = File.OpenRead(path))
            {
                return Hash(fs);
            }
        }

        public string Hash(byte[] data)
        {
            if (data == null) throw new ArgumentNullException(nameof(data));
            using (var ha = CreateHashAlgorithm())
            {
                var t = ha.ComputeHash(data);
                return ToString(t);
            }
        }

        public string Hash(string value, Encoding encoding)
        {
            if (encoding == null) throw new ArgumentNullException(nameof(encoding));
            var d = encoding.GetBytes(value);
            using (var ha = CreateHashAlgorithm())
            {
                var t = ha.ComputeHash(d);
                return ToString(t);
            }
        }

        public string Hash(string value)
        {
            return Hash(value, Encoding.UTF8);
        }

        private string ToString(byte[] data)
        {
            var builder = new StringBuilder();
            foreach (var d in data) builder.Append(d.ToString("x2"));
            return builder.ToString();
        }
    }
}